<template>
  <div class="pu-bpmn-design">
    <el-container>
      <el-container>
        <el-header>
          <cpt-flow-toolbar :bpmn-modeler="bpmnModeler" :reset-diagram="createDiagram">
            <slot slot="toolbar" name="toolbar"></slot>
            <slot slot="toolbar-left" name="toolbar-left"></slot>
            <template slot="toolbar-left">
              <el-button icon="el-icon-cpu" @click="handleSimulation">模拟</el-button>
              <el-button icon="el-icon-view" @click="handlePreview">查看XML</el-button>
              <el-button icon="el-icon-edit-outline" @click="handleImportXml">导入XML</el-button>
            </template>
            <slot slot="toolbar-right" name="toolbar-right"></slot>
          </cpt-flow-toolbar>
        </el-header>
        <el-main>
          <div class="pu-bpmn-design-container" ref="flowDesignContainer"></div>
        </el-main>
      </el-container>

      <el-aside :width="panelWidthClone + 'px'" class="cfd-panel">
        <cpt-properties-panel ref="propertiesPanel"
                              :bpmn-modeler="bpmnModeler"
                              :functions="functions"
                              :forms="forms"
                              :fields="fields"
                              :buttons="buttons"
                              :execution-listeners="executionListeners"
                              :task-listeners="taskListeners"
                              :conditionAssists="conditionAssists"></cpt-properties-panel>
        <div class="cfd-panel-collapse-btn" @click="panelWidthClone = (panelWidthClone === 0 ? panelWidth : 0)">
          <i :class="(panelWidthClone === 0 ? 'el-icon-arrow-left' : 'el-icon-arrow-right')"></i>
        </div>
      </el-aside>

    </el-container>

    <el-drawer title="查看XML" class="c-f-d-drawer" :visible.sync="viewXmlVisible" append-to-body destroy-on-close
               :wrapper-closable="false" size="60%">
      <div class="c-f-d-drawer-pre">
        <highlightjs ref="preview" class="c-f-d-drawer-pre-view" language="xml" :code="xml"/>
        <div class="c-f-d-drawer-tools">
          <el-button class="copy_dom" type="primary" @click="handlePreviewCopy">复 制</el-button>
        </div>
      </div>
    </el-drawer>

    <el-drawer title="模拟" class="c-f-d-drawer" :visible.sync="viewerVisible" append-to-body destroy-on-close
               :wrapper-closable="false" size="80%">
      <pu-flow-viewer ref="flowViewer" :xml="xml" load-simulation-module load-minimap-module/>
    </el-drawer>

    <cpt-flow-import ref="flowImport" :bpmn-modeler="bpmnModeler"/>
  </div>
</template>

<script>

// 自定义模型
import CustomModeler from './additional-modules/CustomBpmnModeler';
// 工具栏
import CptFlowToolbar from './components/toolbar'
// 右侧编辑
import CptPropertiesPanel from './custom-panel/PropertiesPanel';
// 预览
import CptFlowViewer from './components/viewer'
// 导入xml
import CptFlowImport from './components/import'
// 默认xml
import DefaultEmptyXML from "./util/defaultEmpty";
// 工具函数
import {
  addOrUpdateAllFormProperty,
  addOrUpdateUserTaskButton,
  deleteAllFormProperty,
  deleteUserTaskAllButton,
  updateBaseInfo
} from "./util/utils";
//
import Emitter from "./util/emitter";
import 'highlight.js/styles/an-old-hope.css'

export default {
  name: "pu-bpmn-design",
  components: {
    CptFlowToolbar,
    CptFlowViewer,
    CptFlowImport,
    CptPropertiesPanel
  },
  mixins: [Emitter],
  props: {
    processId: String,
    processName: String,
    prefix: {
      type: String,
      default: "flowable"
    },
    processXml: String,
    // 功能集合
    functions: {
      type: Array,
      default() {
        return [
          {
            title: "系统功能",
            codes: []
          },
          {
            title: "自定义功能",
            codes: []
          }
        ];
      }
    },
    // 节点类型
    nodeTypes: {
      type: Array,
      default() {
        return [
          {"label": "申请", "value": "${pass}"},
          {"label": "审核中", "value": "${!pass}"},
          {"label": "审批中", "value": "${nrOfMultiInstanceResult}"},
          {"label": "", "value": "${!nrOfMultiInstanceResult}"}
        ]
      }
    },
    formKey: String,
    extFormKey: String,
    // 表单数据源
    forms: Array,
    // 默认字段数据源
    fields: Array,
    // 按钮数据源
    buttons: Array,
    // 任务监听数据源
    taskListeners: {
      type: Array,
      default() {
        return [];
      }
    },
    // 执行监听数据源
    executionListeners: {
      type: Array,
      default() {
        return [];
      }
    },
    conditionAssists: {
      type: Array,
      default() {
        return [
          {"label": "通过", "value": "${pass}"},
          {"label": "驳回", "value": "${!pass}"},
          {"label": "会签通过", "value": "${nrOfMultiInstanceResult}"},
          {"label": "会签未通过", "value": "${!nrOfMultiInstanceResult}"}
        ]
      }
    },
    events: {
      type: Array,
      default: () => ["element.click"]
    },
    drawerWidth: {
      type: Number,
      default: 500
    },
    // 右侧边栏宽度
    panelWidth: {
      type: Number,
      default: 430
    }
  },
  provide() {
    return {
      prefix: this.prefix,
      width: this.drawerWidth,
      formKey: this.formKey,
      extFormKey: this.extFormKey
    };
  },
  data() {
    return {
      bpmnModeler: null,
      formActiveName: 'process', // 表单激活标识
      scale: 1, // 放大缩小
      xml: "", // xml数据
      viewerVisible: false,
      viewXmlVisible: false,
      importXmlVisible: false,
      panelWidthClone: 430
    }
  },
  watch: {
    processXml: {
      deep: true,
      handler(processXml) {
        this.$nextTick(() => this.createDiagram(processXml));
      }
    }
  },
  created() {
    this.initChildEvent();
  },
  mounted() {
    this.panelWidthClone = this.panelWidth;
    this.initBpmnModeler();
    this.$once("hook:beforeDestroy", () => {
      if (this.bpmnModeler) this.bpmnModeler.destroy();
      this.$emit("destroy", this.bpmnModeler);
      this.bpmnModeler = null;
    });
  },
  methods: {
    initBpmnModeler() {
      // 避免缓存，每次清一下
      this.bpmnModeler && this.bpmnModeler.destroy();
      this.bpmnModeler = new CustomModeler({
        container: this.$refs["flowDesignContainer"],
        // 键盘快捷键
        keyboard: {
          bindTo: window
        }
      });

      // 暴露实例
      window.bpmnInstances = {
        modeler: this.bpmnModeler,
        modeling: this.bpmnModeler.get("modeling"),
        moddle: this.bpmnModeler.get("moddle"),
        eventBus: this.bpmnModeler.get("eventBus"),
        bpmnFactory: this.bpmnModeler.get("bpmnFactory"),
        elementFactory: this.bpmnModeler.get("elementFactory"),
        elementRegistry: this.bpmnModeler.get("elementRegistry"),
        replace: this.bpmnModeler.get("replace"),
        selection: this.bpmnModeler.get("selection")
      };

      this.initModelListeners();
      this.createDiagram(this.processXml);
      this.$emit("init-finished", this.bpmnModeler);
    },
    // 初始化流程
    async createDiagram(xml) {
      try {
        let newId = this.processId || `Process_${new Date().getTime()}`;
        let newName = this.processName || `Name_${new Date().getTime()}`;
        let xmlString = xml || DefaultEmptyXML(newId, newName, this.prefix);
        //
        let {warnings} = await this.bpmnModeler.importXML(xmlString);
        if (warnings && warnings.length) {
          warnings.forEach(warn => console.warn(warn));
        }
      } catch (e) {
        console.error(`[Process Designer Warn]: ${e?.message || e}`);
      }
    },
    initModelListeners() {
      const EventBus = this.bpmnModeler.get("eventBus");
      const that = this;
      // 注册需要的监听事件, 将. 替换为 - , 避免解析异常
      this.events.forEach(event => {
        EventBus.on(event, function (eventObj) {
          let eventName = event.replace(/\./g, "-");
          let element = eventObj ? eventObj.element : null;
          that.$emit(eventName, element, eventObj);
        });
      });
      // 监听图形改变返回xml
      EventBus.on("commandStack.changed", async event => {
        try {
          let {xml} = await this.bpmnModeler.saveXML({format: true});
          this.xml = xml;
          this.$emit("commandStack-changed", event);
          this.$emit("change", xml);
        } catch (e) {
          console.error(`[Process Designer Warn]: ${e.message || e}`);
        }
      });

      // 监听元素新增
      EventBus.on("commandStack.shape.create.postExecute", ({context}) => {
        if (context.shape.type === "bpmn:UserTask") {
          // 添加用户任务节点按钮
          addOrUpdateUserTaskButton(context.shape, this.buttons, this.prefix);
          // 添加或修改用户任务节点字段
          addOrUpdateAllFormProperty(context.shape, this.fields, this.prefix);
          // 更新
          if (!this.formKey && this.extFormKey) {
            updateBaseInfo(context.shape, "extFormKey", this.extFormKey);
          }
        }
        // 开始节点
        if (context.shape.type === "bpmn:StartEvent") {
          updateBaseInfo(context.shape, "name", "开始");
          updateBaseInfo(context.shape, "initiator", "initiator");
          if (this.formKey) {
            updateBaseInfo(context.shape, "formKey", this.formKey);
          }
        }
        this.$emit("shape-create", context);
      });

      // 导入xml完成回调
      this.bpmnModeler.on("import.done", () => {
        this.updateStartEventInitiator("initiator");
        // this.addOrUpdateUserTaskButton(this.buttons);
        // this.addOrUpdateAllFormProperty(this.fields);
        if (this.formKey) {
          this.updateStartEventFormKey(this.formKey);
          this.updateProcessFormType("internal");
        } else if (this.extFormKey) {
          this.updateUserTaskExtFormKey(this.extFormKey);
          this.updateProcessFormType("external");
        } else {
          this.updateProcessFormType("indep");
        }
        this.$emit("import-done");
      });
    },
    // 预览
    async handleSimulation() {
      await this.getXml();
      this.viewerVisible = true;
    },
    // 查看xml
    async handlePreview() {
      await this.getXml();
      this.viewXmlVisible = true;
    },
    async handlePreviewCopy() {
      let xml = await this.getXml();
      const aux = document.createElement('pre');
      aux.innerText = xml;
      document.body.appendChild(aux);
      if (document.body.createTextRange) {
        let range = document.body.createTextRange();
        range.moveToElementText(aux);
        range.select();
      } else if (window.getSelection) {
        let selection = window.getSelection();
        let range = document.createRange();
        range.selectNodeContents(aux);
        selection.removeAllRanges();
        selection.addRange(range);
      } else {
        alert("您的浏览器不支持");
      }
      document.execCommand('copy')
      document.body.removeChild(aux)
      this.$message.success('复制成功');
    },
    // 导入xml
    handleImportXml() {
      this.$refs.flowImport.show();
    },
    // 获取流程模型对象
    getBpmnModeler() {
      return this.bpmnModeler;
    },
    // 获取xml
    async getXml() {
      const result = await this.bpmnModeler.saveXML({format: true});
      const {xml} = result;
      this.xml = xml;
      return xml;
    },
    // 获取流程信息
    getModelData() {
      const businessObject = this.bpmnModeler.get("elementRegistry").find(el => el.type === "bpmn:Process").businessObject;
      return {id: businessObject.id, name: businessObject.name};
    },
    // 导入xml
    async importXML(data) {
      // 将字符串转换成图显示出来
      data = data.replace(/<!\[CDATA\[(.+?)]]>/g, function (match, str) {
        return str.replace(/</g, '&lt;')
      })
      try {
        let {warnings} = await this.bpmnModeler.importXML(data)
        if (warnings && warnings.length) {
          warnings.forEach(warn => console.warn(warn));
        }
      } catch (e) {
        console.error(`[Process Designer Warn]: ${e?.message || e}`);
      }
    },
    // 更新流程表单类型标识
    updateProcessFormType(formType) {
      let processEl = this.bpmnModeler.get("elementRegistry").find(el => el.type === "bpmn:Process");
      updateBaseInfo(processEl, "formType", formType);
    },
    // 更新流程启动事件节点initiator
    updateStartEventInitiator(initiator) {
      let userTaskList = this.bpmnModeler.get("elementRegistry").filter(el => el.type === "bpmn:StartEvent");
      updateBaseInfo(userTaskList, "initiator", initiator);
    },
    // 更新流程启动事件节点formKey
    updateStartEventFormKey(formKey) {
      let userTaskList = this.bpmnModeler.get("elementRegistry").filter(el => el.type === "bpmn:StartEvent");
      updateBaseInfo(userTaskList, "formKey", formKey);
    },
    // 更新流程用户任务节点formKey
    updateUserTaskExtFormKey(extFormKey) {
      let userTaskList = this.bpmnModeler.get("elementRegistry").filter(el => el.type === "bpmn:UserTask" || el.type === "bpmn:StartEvent");
      updateBaseInfo(userTaskList, "extFormKey", extFormKey);
    },
    // 更新流程用户任务节点button
    addOrUpdateUserTaskButton(buttons = []) {
      let userTaskList = this.bpmnModeler.get("elementRegistry").filter(el => el.type === "bpmn:UserTask");
      addOrUpdateUserTaskButton(userTaskList, buttons, this.prefix);
    },
    deleteAllUserTaskButton() {
      let userTaskList = this.bpmnModeler.get("elementRegistry").filter(el => el.type === "bpmn:UserTask");
      deleteUserTaskAllButton(userTaskList, this.prefix);
    },
    // 更新流程用户任务节点FormProperty
    addOrUpdateAllFormProperty(fields = []) {
      let userTaskList = this.bpmnModeler.get("elementRegistry").getAll();
      addOrUpdateAllFormProperty(userTaskList, fields, this.prefix);
    },
    deleteAllFormProperty() {
      let userTaskList = this.bpmnModeler.get("elementRegistry").getAll();
      deleteAllFormProperty(userTaskList, this.prefix);
    },
    // 子组件事件-----------------------------------------
    initChildEvent() {
      this.$on("user-task-select-assignee", (param) => {
        this.$emit("select-assignee", param.tagsData, param.resolve);
      });
      this.$on("user-task-select-user", (param) => {
        this.$emit("select-user", param.tagsData, param.resolve);
      });
      this.$on("user-task-select-role", (param) => {
        this.$emit("select-role", param.tagsData, param.resolve);
      });
      this.$on("user-task-select-dept", (param) => {
        this.$emit("select-dept", param.tagsData, param.resolve);
      });
      this.$on("user-task-select-post", (param) => {
        this.$emit("select-post", param.tagsData, param.resolve);
      });
    },
    updateUserTaskIdmAssignee(users) {
      this.broadcast("UserTask", "updateIdmAssignee", users);
    },
    updateUserTaskIdmUser(users) {
      this.broadcast("UserTask", "updateIdmUser", users);
    },
    updateUserTaskIdmRole(roles) {
      this.broadcast("UserTask", "updateIdmRole", roles);
    },
    updateUserTaskIdmDept(depts) {
      this.broadcast("UserTask", "updateIdmDept", depts);
    },
    updateUserTaskIdmPost(posts) {
      this.broadcast("UserTask", "updateIdmPost", posts);
    }
  }
}
</script>

<style lang="scss">
@import 'main';
</style>
